package javango.core.servers;

import javango.api.MethodObjectParams;
import javango.api.Urls;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import javango.core.AnonymousUser;
import javango.core.LoginRequired;
import javango.core.RoleRequired;
import javango.api.Url;
import javango.core.contextprocessors.ContextProcessor;
import javango.http.HttpException;
import javango.http.HttpRequest;
import javango.http.HttpResponse;
import javango.http.SimpleHttpResponse;
import javango.middleware.Middleware;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import java.lang.reflect.InvocationTargetException;
import javango.conf.RuntimeSettings;

@Singleton
public class RequestProcessor {
	
	public static interface Listener {
		public void stopRequestProcessor(RequestProcessor requestProcessor);
	}
	
	private final static Log log = LogFactory.getLog(RequestProcessor.class);
	
	protected List<Listener> listeners = new ArrayList<Listener>();
	
    private RuntimeSettings settings;
    
    private Injector injector;
	
    /**
     * Cached list of urls,  each url will have its members injected by Guice once and only once when this list is first initizlized by the first call to getUrls().
     */
    private Urls urls;
    
	@Inject
	public RequestProcessor(RuntimeSettings settings, Injector injector) throws Exception {
		this.settings = settings;
        this.injector = injector;
	}

	public void stop() {
		for (Listener l : listeners) {
			l.stopRequestProcessor(null);
		}
		
	}
	
	/**
	 * Returns a list of urls for this application,  the list is cached on first access,  all
	 * urls in the list will be injected by guice.
	 * 
	 * @return
	 */
	protected Urls getUrls() {
		if (urls == null) {
			urls = injector.getInstance(settings.getSettings().getUrlsClass());
			for(Url u : urls.getUrlPatterns()) {
				injector.injectMembers(u);
			}
		}
		return urls;
	}
	public HttpResponse service(final HttpRequest request) throws HttpException {
		String path = request.getPath();			
		if (log.isDebugEnabled()) log.debug("Processing path '" + path + "'" );

		ListIterator<Middleware> middlewareIterator=settings.getMiddlewares().listIterator();
		for (; middlewareIterator.hasNext(); ) {
			Middleware m = middlewareIterator.next();
			HttpResponse r = m.processRequest(request);
			if (r != null) {
				return r;
			}
		}
		
		HttpResponse response = null;
		Urls urls = getUrls();
		for(Url url: urls.getUrlPatterns()) {
			if (url.matches(path)) {
				try {
					MethodObjectParams mop = url.getMethodObjectParams(path, request);

					for (Middleware m : settings.getMiddlewares()) {
						m.processMethodAndParams(request, mop);
					}
					
					// TODO Move somewhere else
					for (Annotation annotation : mop.getMethod().getAnnotations()) {
						// TODO Should this return the response,  or let it fall through to the middleware?
						if (annotation instanceof LoginRequired) {
							if (request.getUser() == null || request.getUser() instanceof AnonymousUser) {
								response = new SimpleHttpResponse("Login Required");
							}
						} else if (annotation instanceof RoleRequired) {
							RoleRequired rr = (RoleRequired)annotation;
							if (request.getUser() == null || request.getUser() instanceof AnonymousUser || !request.getUser().hasRole(rr.role())) {							
								response = new SimpleHttpResponse("Not Authorized");
							}
						}
					}
					
					if (response == null ) response = invoke(mop, injector);
					break;
				} catch (Throwable t) {
					for (; middlewareIterator.hasPrevious(); ) {
						Middleware m = middlewareIterator.previous();
						response = m.processException(request, t);
						if( response != null) {
							return response;
						}
					}
					throw new HttpException(t);
				}
			}
		}
		
		if (response != null && response.getContext() != null) {
			// TODO move message handling to template context processor
			// should be an object that will automatically remove messages/errors if they are read from the template
			response.getContext().put("messages", request.getSession().getAndClearMessages());
			response.getContext().put("errors", request.getSession().getAndClearErrors());
			response.getContext().put("absolute_path", request.getContext());
			
			for (ContextProcessor rc : settings.getContextProcessors() ) {
				response.getContext().putAll(rc.evaluate(request));
			}
		}
		
		for (; middlewareIterator.hasPrevious(); ) {
			Middleware m = middlewareIterator.previous();
			response = m.processResponse(request, response);
		}
		
		if (response != null) {
			return response;
		}
				
		throw new HttpException("No URLs matched: '" + path + "'");
	}
	
    public static HttpResponse invoke(MethodObjectParams m, Injector injector) throws HttpException {
        try {
            Object object = injector.getInstance(m.getClazz());
            return (HttpResponse) m.getMethod().invoke(object, m.getArguments());
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof Exception) {
                throw new HttpException(e.getTargetException());
            }
            throw new HttpException(e);
        } catch (Exception e) {
            throw new HttpException(e);
        }
    }
    
}
